package grpc

import (
	"context"
	"crypto/tls"
	"net"

	"gitee.com/zawei/yggdrasil/pkg/log"
	"gitee.com/zawei/yggdrasil/pkg/transport"
	"gitee.com/zawei/yggdrasil/pkg/transport/server"
	"gitee.com/zawei/yggdrasil/pkg/utils/xnet"
	"google.golang.org/grpc"
)

type grpcServer struct {
	cfg    *Config
	ln     net.Listener
	server *grpc.Server
	info   *server.Info
}

func newServer(config *Config) *grpcServer {
	config.serverOptions = append(config.serverOptions,
		grpc.StreamInterceptor(chainStreamServer(config.streamInterceptors...)),
		grpc.UnaryInterceptor(chainUnaryServer(config.unaryInterceptors...)),
	)
	var (
		ln  net.Listener
		err error
	)
	if config.TLS != nil {
		tlsConfig, err := config.TLS.ServerTLSConfig()
		if err != nil {
			log.Fatalw("fault to get tls config", "error", err)
		}
		ln, err = tls.Listen(config.Network, config.Address(), tlsConfig)
	} else {
		ln, err = net.Listen(config.Network, config.Address())
	}
	if err != nil {
		log.Fatalw("fault to get listener", "error", err)
	}
	config.Host, config.Port = xnet.GetHostAndPortByAddr(ln.Addr())
	info := server.NewInfo(
		server.WithScheme("grpc"),
		server.WithAddress(config.Host, config.Port),
		server.WithKind(transport.ServiceProvider),
	)

	return &grpcServer{
		cfg:  config,
		ln:   ln,
		info: &info,
	}
}

func (s *grpcServer) Init() error {
	s.server = grpc.NewServer(s.cfg.serverOptions...)
	for _, serviceDesc := range s.cfg.serviceDesc {
		desc := serviceDesc.desc
		s.server.RegisterService(serviceDesc.desc, serviceDesc.svr)
		service := &server.Service{
			Name:    desc.ServiceName,
			Methods: make([]string, len(desc.Methods)),
		}
		for i, method := range desc.Methods {
			service.Methods[i] = method.MethodName
		}
		s.info.Services[desc.ServiceName] = service
	}
	return nil
}

func (s *grpcServer) Start() error {
	return s.server.Serve(s.ln)
}

func (s *grpcServer) Stop() error {
	s.server.Stop()
	return nil
}

func (s *grpcServer) GracefulStop(_ context.Context) error {
	s.server.GracefulStop()
	return nil
}

func (s *grpcServer) Info() *server.Info {
	return s.info
}
